#include "stdafx.h"
#include "SceneLoader.h"

#include "tinyxml\tinyxml.h"

#include "Scene.h"
#include "Node.h"
#include "MultiObjectNode.h"
#include "NodeUtil.h"
#include "WorldData.h"

#include "RenderingSystem.h"
#include "MeshInstance.h"
#include "LoadTexture.h"
#include "Material.h"
#include "Light.h"
#include "ParticleSystem.h"

#include "PhysicsSystem.h"
#include "PhysicsPrimitives.h"



using namespace mscene;

Scene * gScene;
mgfx::RenderingSystem * gRendSys;
mphys::PhysicsSystem * gPhysSys;

const WorldData * gWorldData;

static bool strToBool(const std::string & s)
{
	if(s == "true") return true;
	if(s == "false") return false;
	LOG_STREAM_ERROR("Parse error: expected bool. Found instead: " << s);
	return false;
}

static void parseNodeData(TiXmlElement * parent, PrimitiveNodeDesc & outDesc)
{
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "position")
		{
			outDesc.Position.x = (float)atof(elem->Attribute("x"));
			outDesc.Position.y = (float)atof(elem->Attribute("y"));
			outDesc.Position.z = (float)atof(elem->Attribute("z"));
		}
		else if (elemName == "orientation")
		{
			outDesc.Orientation.x = (float)atof(elem->Attribute("x"));
			outDesc.Orientation.y = (float)atof(elem->Attribute("y"));
			outDesc.Orientation.z = (float)atof(elem->Attribute("z"));
			outDesc.Orientation.w = (float)atof(elem->Attribute("w"));
		}
		else if(elemName == "scale")
		{
			outDesc.Scale.x = (float)atof(elem->Attribute("x"));
			outDesc.Scale.y = (float)atof(elem->Attribute("y"));
			outDesc.Scale.z = (float)atof(elem->Attribute("z"));
		}
	}
}

static void parseNode(TiXmlElement * parent, Node * outNode)
{
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "position")
		{
			outNode->Position.x = (float)atof(elem->Attribute("x"));
			outNode->Position.y = (float)atof(elem->Attribute("y"));
			outNode->Position.z = (float)atof(elem->Attribute("z"));
		}
		else if (elemName == "orientation")
		{
			outNode->Orientation.x = (float)atof(elem->Attribute("x"));
			outNode->Orientation.y = (float)atof(elem->Attribute("y"));
			outNode->Orientation.z = (float)atof(elem->Attribute("z"));
			outNode->Orientation.w = (float)atof(elem->Attribute("w"));
		}
		else if(elemName == "scale")
		{
			outNode->Scale.x = (float)atof(elem->Attribute("x"));
			outNode->Scale.y = (float)atof(elem->Attribute("y"));
			outNode->Scale.z = (float)atof(elem->Attribute("z"));
		}
	}
}

static mgfx::PointLight* parsePointLight(TiXmlElement * parent)
{
	mgfx::PointLightDesc lightDesc;
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "radius") lightDesc.Radius = (float)atof(elem->Attribute("value"));
		else if(elemName == "falloff") lightDesc.Falloff = (float)atof(elem->Attribute("value"));
		else if(elemName == "intensity") lightDesc.Intensity = (float)atof(elem->Attribute("value"));
		else if(elemName == "color")
		{
			lightDesc.Color.x = (float)atof(elem->Attribute("r"));
			lightDesc.Color.y = (float)atof(elem->Attribute("g"));
			lightDesc.Color.z = (float)atof(elem->Attribute("b"));
		}

	}
	return gRendSys->createPointLight(lightDesc);
}

static mgfx::SpotLight* parseSpotLight(TiXmlElement * parent)
{
	mgfx::SpotLightDesc lightDesc;
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "radius") lightDesc.Radius = (float)atof(elem->Attribute("value"));
		else if(elemName == "falloff") lightDesc.Falloff = (float)atof(elem->Attribute("value"));
		else if(elemName == "intensity") lightDesc.Intensity = (float)atof(elem->Attribute("value"));
		else if(elemName == "color")
		{
			lightDesc.Color.x = (float)atof(elem->Attribute("r"));
			lightDesc.Color.y = (float)atof(elem->Attribute("g"));
			lightDesc.Color.z = (float)atof(elem->Attribute("b"));
		}
		else if(elemName == "spotangle") lightDesc.SpotAngleRad = (float)atof(elem->Attribute("value"));
		else if(elemName == "spotfalloff") lightDesc.SpotFalloff = (float)atof(elem->Attribute("value"));
		else if(elemName == "iscastingshadows") lightDesc.IsCastingShadows = strToBool(elem->Attribute("value"));

	}

	return gRendSys->createSpotLight(lightDesc);
}

static mgfx::ParticleSystem* parseParticleSystem(TiXmlElement * parent)
{
	mgfx::ParticleSystemDesc psDesc;
	psDesc.initDefault();

	return gRendSys->createParticleSystem(psDesc);
}

static void parsePhysicsData(TiXmlElement * parent, PrimitiveNodeDesc & outDesc)
{
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "rigidbody") outDesc.IsStatic = strToBool(elem->Attribute("static"));
	}
}

static void parseMaterialData(TiXmlElement * parent, PrimitiveNodeDesc & outDesc)
{
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "color") 
		{
			outDesc.Color.x = (float)atof(elem->Attribute("r"));
			outDesc.Color.y = (float)atof(elem->Attribute("g"));
			outDesc.Color.z = (float)atof(elem->Attribute("b"));
		}
	}
}

static PrimitiveNodeDesc parseAndAddPrimitiveNode(TiXmlElement * parent)
{
	PrimitiveNodeDesc nodeDesc;
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "nodedata")
			parseNodeData(elem, nodeDesc);
		else if(elemName == "physicsdata")
			parsePhysicsData(elem, nodeDesc);
		else if(elemName == "materialdata")
			parseMaterialData(elem, nodeDesc);
	}
	return nodeDesc;
}

static void parseAndAddPlane(TiXmlElement * parent)
{
	PrimitiveNodeDesc nodeDesc = parseAndAddPrimitiveNode(parent);
	createPlaneNode(nodeDesc, *gWorldData);
}

static void parseAndAddBox(TiXmlElement * parent)
{
	PrimitiveNodeDesc nodeDesc = parseAndAddPrimitiveNode(parent);
	createBoxNode(nodeDesc, *gWorldData);
}

static void parseAndAddSphere(TiXmlElement * parent)
{
	PrimitiveNodeDesc nodeDesc = parseAndAddPrimitiveNode(parent);
	createSphereNode(nodeDesc, *gWorldData);
}

static void parseAndAddMultiNode(TiXmlElement * parent)
{
	mscene::MultiObjectNode * node = new mscene::MultiObjectNode(*gScene);
	for(TiXmlElement* elem = parent->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "nodedata")
			parseNode(elem, node);
		else if(elemName == "pointlightdata")
		{
			mgfx::PointLight * light = parsePointLight(elem);
			node->addGfxObject(*light);
		}
		else if(elemName == "spotlightdata")
		{
			mgfx::SpotLight * light = parseSpotLight(elem);
			node->addGfxObject(*light);
		}
		else if(elemName == "particlesystem")
		{
			node->addGfxObject(*parseParticleSystem(elem));
		}
	}
	gScene->addNode(*node);
}

void mscene::loadScene(const std::string & file, const WorldData data)
{
	gScene = data.Scene;
	gRendSys = data.RenderSystem;
	gPhysSys = data.PhysicsSystem;

	gWorldData = &data;

	TiXmlDocument doc;
	if(!doc.LoadFile(file.c_str()))
	{
		LOG_STREAM_ERROR("Opening scene file failed: " << file);
	}

	TiXmlElement* root = doc.FirstChildElement();

	if(root == NULL)
	{
		LOG_STREAM_ERROR("Failed to load file: No root element.");
		doc.Clear();
	}

	for(TiXmlElement* elem = root->FirstChildElement(); elem != NULL; elem = elem->NextSiblingElement())
	{
		std::string elemName = elem->Value();
		if(elemName == "plane") parseAndAddPlane(elem);
		else if(elemName == "box") parseAndAddBox(elem);
		else if(elemName == "sphere") parseAndAddSphere(elem);
		else if(elemName == "multinode") parseAndAddMultiNode(elem);
	}
}